想要蓋一棟房子,可不是只需要雙手就能辦到,還需要許多工具來一起完成。我們可以將會使用到的套件在建立 image 時一同裝好,再將不同的服務進行整合,解放雙手來一鍵啟動。
本篇除了要把相關套件安裝至容器,還會帶大家一步一步地從 Dockerfile 進化至 Docker compose,把 Django 與其他服務交給多容器管理工具來打理。
requirements.txt 是 Python 使用 pip 安裝時會需要的管理工具,現在會先將之後使用到的套件一併安裝(有些目前還不會使用到)。
建立一個名為 requirements.txt 的文件
cd ~/exmaple_tenant
touch requirements.txt
寫入以下內容,這些是接下來將會使用到的 Python 套件
django-tenants==3.4.3
django==3.2.5
django-tenants-q==1.0.0
django-elasticsearch-dsl==7.1.1
django_q==1.3.9
django_redis==5.2.0
psycopg2==2.9.3
uwsgi==2.0.20
接續上一篇文章『建造地基!使用 Docker』的 Dockerfile,我們要把會使用到的相關套件安裝至容器,除了有上方的 python 套件,還有 PostgreSQL 的相依套件。
更新 Dockerfile
FROM ubuntu:22.04
# set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV DEBIAN_FRONTEND=noninteractive TZ="Asia/Taipei"
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
# Install python 3.10
RUN apt-get update -y
RUN apt-get install gcc python3.10 python3.10-dev -y
# # Install PostgresSQL
RUN apt-get update && apt-get -y install postgresql libpq-dev postgresql-client postgresql-client-common
RUN apt-get update && apt-get -y install python3-psycopg2 gettext
# Install pip
RUN apt-get install curl -y
RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
RUN python3.10 get-pip.py
# set work directory
RUN mkdir -p /opt/app
COPY . /opt/app
RUN pip install -r /opt/app/requirements.txt
建立 Image
docker build -t example_tenant . # 使用當前目錄的 Dockerfile 建立容器
使用 run 運行 container,這裡使用了許多參數,
-u 使用當前非 root 使用者(提示找不到名稱是正常的)
-t 分配一個虛擬終端
-i 讓容器的標準輸入保持打開
-p 進行 port 對映,可從本地主機的 port 連到容器中的 port
-v 將資料進行映射,本地主機的指定目錄對映 container 中的指定目錄(本地主機的為主)
docker run -it -u $(id -u):$(id -g) \
--name example_tenant \
-p 8000:8000 \
-v ~/example_tenant:/opt/app \
example_tenant /bin/bash
建立 Django 專案
cd /opt/app/ # 切換到我們複製 requirements.txt 的目錄
django-admin startproject main # 建立 Django Project
ls -al # 查看當前目錄
./
../
Dockerfile
main/ # 新建的 Django Project
requirements.txt
測試運行
mv main web # 調整目錄名稱
cd web
python3.10 manage.py migrate
python3.10 manage.py runserver 0.0.0.0:8000
...
System check identified no issues (0 silenced).
September 03, 2022 - 10:13:43
Django version 3.2.5, using settings 'example_tenant.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
運行成功!
專案建立後,每次啟動服務後都要手動執行 runserver 是很繁瑣的,讓我們來變一下魔法吧!
本地主機建立 docker-entrypoint.sh,讓容器啟動的時候自動執行指令
cd ~/example_tenant
touch docker-entrypoint.sh
chmod 755 docker-entrypoint.sh # 調整權限
寫入以下內容
#!/bin/bash
cd /opt/app/web/
# Apply database migrations
echo "Apply database migrations"
python3.10 manage.py migrate
# Start server
echo "Starting server"
python3.10 manage.py runserver 0.0.0.0:8000
exec "$@"
加入 Dockerfile
FROM ubuntu:22.04
...
RUN chmod +x /opt/app/docker-entrypoint.sh
ENTRYPOINT [ "/opt/app/docker-entrypoint.sh" ]
重新 build
docker build -t example_tenant . # 使用當前目錄的 Dockerfile 建立容器
運行容器
使用 -d 讓容器在背景運行
docker stop example_container # 停止原本的容器
docker rm example_container # 刪除原本的容器
docker run -itd --name example_container \
-p 8000:8000 \
-v ~/example_tenant:/opt/app \
example_tenant /bin/bash
entrypoint 完成了,我們接著進入下一步
用 docker run 的方式可以達到我們的目的,但是一次只能啟動一個容器,且都要打許多參數。使用 Docker-compose 可以更輕鬆更乾淨的幫助我們運行服務。
建立 docker-compose.yml
cd ~/example_tenant
touch docker-compose.yml
寫入以下內容
version: "3.9"
services:
web:
image: example_tenant:latest # image 名稱
build: . # 當 --build 執行時的 build 執行目錄
container_name: example_container_web # 容器名稱
restart: always # 自動重啟
user: "1000" # 指定非 root 使用者
ports:
- "8000:8000" # port 映射
volumes:
- .:/opt/app # 資料卷對映(. 為當前目錄)
運行 docker-compose
docker stop example_container # 停止原本的容器
docker rm example_container # 刪除原本的容器
docker compose up -d # 背景執行
...
[+] Running 2/2
⠿ Network example_tenant_default Created 0.1s
⠿ Container example_container_web Started
運行成功!
若要使用多 schema 架構,使用 Django 自帶的 SQLite 是不足夠的,現在我們來安裝 PostgreSQL。
將原先的 docker-compose.yml 修改為以下內容,加入 db 服務
version: "3.9"
services:
db:
image: postgres
container_name: example_tenant_db
restart: always
environment:
- POSTGRES_USER=db_user
- POSTGRES_PASSWORD=db_password
- POSTGRES_DB=db_name
healthcheck:
test: [ "CMD", "pg_isready", "-q", "-d", "db_name", "-U", "db_user" ]
timeout: 20s
interval: 10s
retries: 10
volumes:
- ./data/postgresql/data:/var/lib/postgresql/data
web:
image: example_tenant:latest
build: .
container_name: example_container_web
restart: always
user: "1000"
ports:
- "8000:8000"
volumes:
- .:/opt/app
depends_on:
db:
condition: service_healthy
links:
- db
運行 docker- compose
docker-compose down -v
docker-compose up -d
...
[+] Running 3/3
⠿ Network example_tenant_default Created
⠿ Container example_container_db Healthy
⠿ Container example_container_web Started
運行成功!
當 compose 文件中有敏感資訊或是會經常調整的參數時,使用 .env 可以幫助我們很好的解決問題,讓我們來看看要怎麼進行參數化吧!
準備 .env file
cd ~/example_tenant
touch .env
在 .env 寫入參數
# Docker Compose .env
IMAGE_NAME=example_tenant
POSTGRES_DB=db_name
POSTGRES_USER=db_user
POSTGRES_PASSWORD=db_password
WEB_PORT=8000
UID=1000
調整 docker-compose.yml
version: "3.9"
services:
db:
image: postgres
container_name: ${IMAGE_NAME}_db
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
healthcheck:
test: [ "CMD", "pg_isready", "-q", "-d", "${POSTGRES_DB}", "-U", "${POSTGRES_USER}" ]
timeout: 20s
interval: 10s
retries: 10
volumes:
- ./data/postgresql/data:/var/lib/postgresql/data
web:
image: example_tenant:latest
build: .
container_name: ${IMAGE_NAME}_web
restart: always
user: "${UID}"
ports:
- "${WEB_PORT}:8000"
volumes:
- .:/opt/app
depends_on:
db:
condition: service_healthy
links:
- db
重新運行
docker-compose down -v
docker-compose up -d
...
[+] Running 3/3
⠿ Network example_tenant_default Created
⠿ Container example_container_db Healthy
⠿ Container example_container_web Started 11.7s
完成!
一棟 Django 魔法小屋就這樣完成了,今天詳細講解了該如何使用 Docker 打造容器化的環境,接下來我們就要開始『設計格局,Django 多租戶架構』
你好,我是剛剛在學,覺得你的教學很有用
但是我有一點問題
我docker run完後去/opt/app run django-admin startproject main
結果顯示: CommandError: [Errno 13] Permission denied: '/opt/app/main'
在docker我應該不會用sudo吧
很高興這篇文章有幫助到你~
docker run -it -u $(id -u):$(id -g) \
--name example_tenant \
-p 8000:8000 \
-v ~/example_tenant:/opt/app \
example_tenant /bin/bash
docker run 進入容器環境後我們切換到容器中的 /opt/app 目錄,這個容器中的目錄是對應到本地主機的 ~/example_tenant 目錄。
如果出現 Permission denied 問題可以檢查在本地主機的目錄權限是不是與執行 docker run 的使用者不同。再請你試試看吧!
Local env:
terry:docker
Docker env:
root:nogroup
Docker ENV的是這樣是正常的嗎?
請問我應該是換local的嗎?
#cat /etc/group:
docker:x:1001:terry
#id -u:
1000
#id -g:
1001
主要是看 example_tenant 這個目錄在本地主機的權限設定
本地主機權限對應的的 uid gid 會是容器中的目錄權限,
以我自己的環境為範例,本地主機的 uid 與 gid 與容器中的對應
$ id -u
1000
$ id -g
1000
Local ENV:
cd ~
ls -al
drwxrwxr-x 3 ivankao ivankao 4096 Nov 18 08:20 example_tenant/
Docker ENV
cd /opt
ls -al
drwxrwxr-x 3 1000 1000 4096 Nov 18 16:20 app
如果你 example_tenant 的目錄權限是 terry docker 會對應到 1000 1001
你可以這樣下指令
docker run -it -u 1000:1001 \
--name example_tenant \
-p 8000:8000 \
-v ~/example_tenant:/opt/app \
example_tenant /bin/bash
我CHECK了example_tenant的dir對應的是1000:1001
Terminal用的也是1000:1001
DOCKER RUN的時候也是用1000:1001
但是Permission Denied仍然存在
哈囉,需要的話我可以遠端幫你一起看看問題,我也想知道是什麼原因造成的,要的話請站內簡訊給我聯繫方式~